package com.cat.orm.core.db.dao;

import com.cat.orm.core.base.BasePo;
import com.cat.orm.core.base.Clip;
import com.cat.orm.core.base.IBaseData;
import com.cat.orm.core.convert.PersistenceDataConverter;
import com.github.benmanes.caffeine.cache.AsyncCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Ticker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 带有转换器, 延迟保存的的DAO
 * @param <D> 待存储数据
 * @param <P> 转换POJO数据
 */
public class DelayConvertDao<D extends IBaseData, P extends BasePo> extends ConvertDao<D, P>{

    private final Map<Object, Clip<D>> cache = new ConcurrentHashMap<>();

    public DelayConvertDao(Class<P> clazz, JdbcTemplate jdbcTemplate, PersistenceDataConverter<D, P> converter) {
        super(clazz, jdbcTemplate, converter);
    }

    public DelayConvertDao(Class<P> clazz, JdbcTemplate jdbcTemplate, PersistenceDataConverter<D, P> converter, String tableName) {
        super(clazz, jdbcTemplate, converter, tableName);
    }

    public DelayConvertDao(IDao<P> dao, PersistenceDataConverter<D, P> converter) {
        super(dao, converter);
    }

    @Override
    public D selectByKey(Object key) throws Exception{
        if (key == null){
            throw new NullPointerException("selectByKey error. key is null.");
        }
        // 如果缓存中没有数据，异步查询数据库
        Clip<D> clip = cache.get(key);
        if (clip != null){
            return clip.getData();
        }
        return super.selectByKey(key);
    }

    @Override
    public int insert(D d) {
        this.asyncSave(d);
        return 1;
    }

    @Override
    public int replace(D d) {
        this.asyncSave(d);
        return 1;
    }

    @Override
    public int update(D d) {
        this.asyncSave(d);
        return 1;
    }

    @Override
    public int delete(D d) {
        if (d == null) {
            throw new NullPointerException("insert error. bean is null.");
        }
        Object key = converter.getKey(d);
        if (key == null) {
            throw new NullPointerException("insert error.key is null.");
        }
        this.cache.remove(key);
        P pojo = this.converter.data2Po(d);
        return this.dao.delete(pojo);
    }

    @Override
    public int deleteAll() {
        this.cache.clear();
        return this.dao.deleteAll();
    }


    @Override
    public int[] insertBatch(Collection<D> ds) {
        for (D data : ds) {
            asyncSave(data);
        }
        return new int[]{1};
    }

    @Override
    public int[] updateBatch(Collection<D> ds) {
        for (D data : ds) {
            asyncSave(data);
        }
        return new int[]{1};
    }

    @Override
    public int[] deleteBatch(Collection<D> ds) {
        for (D d : ds) {
            Object key = this.converter.getKey(d);
            this.cache.remove(key);
        }
        List<P> pojos = this.converter.dataList2Pos(ds);
        return this.dao.deleteBatch(pojos);
    }

    /**
     * 保存数据对象
     * @param d 待保存的数据
     */
    private void asyncSave(D d){
        if (d == null) {
            throw new NullPointerException("insert error. bean is null.");
        }
        Object key = converter.getKey(d);
        if (key == null) {
            throw new NullPointerException("insert error.key is null.");
        }
        //做个判断, 尽量不创建新对象
        cache.put(key, new Clip<>(d));
    }

    /**
     * 保存数据
     */
    private void saveAll(){
        if (cache.isEmpty()) {
            return;
        }
        Set<Object> waitSaveKeys = cache.keySet();
        for (Object key : waitSaveKeys) {
            Clip<D> clip = cache.get(key);
            if (clip == null) {
                continue;
            }
            if (!clip.isTimeToSave()) {
                continue;
            }
            D data = clip.getData();
            boolean success = false;
            try {
                P po = converter.data2Po(data);
                success = dao.replace(po) == 1;
            } catch (Exception e) {
                logger.error("save data error.key[{}],errorCount[{}]", key, clip.getErrorCount() + 1, e);
            }
            if (success) {
                // 保存成功
                // 若新保存数据 会new另外个clip 不是同1个对象 remove会失败
                cache.remove(key, clip);
            } else {
                // 保存失败
                clip.addErrorCount();
            }
        }
    }

    public void tick(){
        this.saveAll();
    }

}
